/* 
 * File:   ParticleEmiter.cpp
 * Author: RedEyedKiller
 * 
 * Created on 20 Μάϊος 2010, 3:09 μμ
 */


#include "ParticleEmiter.h"
#include "Particle.h"
#include "../Utilities.h"
#include "../sdl/GraphicsCore.h"
#include "../LevelMap.h"
#include <list>

using namespace Math;

namespace physicsSystem
{

namespace particleSystem
{

ParticleEmiter::ParticleEmiter(ParticleEmiterPrototype* prot, const Math::Vector2F& pos, const Math::Vector2F& dir, int id) : position(pos + prot->defaultRelPosition)
, direction(dir)
{
    alive = true;
    active = true;
    currentPart = 0;
    this->id = id;
    prototype = prot;
    recycler = false;
    current = particles.end( );
    boundingShape = NULL;
}

ParticleEmiter::~ParticleEmiter()
{
    SafeRelease<Particle*>( particles );
}

void ParticleEmiter::AddParticles(int threashold)
{
    if( currentPart + threashold < prototype->maxParts )
    {
        for( int i = 0; i < threashold; i++ )
        {
            Particle* part = new Particle;
            InitializeParticle( part );
            particles.push_back( part );
        }
        currentPart += threashold;
        return;
    }
    else if( currentPart < prototype->maxParts )//more particles fit in but they
    { //are less tha creationThreashold
        for( int i = 0; i < prototype->maxParts - currentPart; i++ )
        {
            Particle* part = new Particle;
            InitializeParticle( part );
            particles.push_back( part );
        }
        currentPart = prototype->maxParts;
        return;
    }
}

void ParticleEmiter::AddParticlesViaRecycle(int threashold)
{
    if( current == particles.end( ) )
    {
        current = particles.begin( );
    }

    if( currentPart + threashold < prototype->maxParts )
    {
        for( int i = 0; i < threashold && current != particles.end( ); i++, ++current )
        {
            Particle* part = *current;
            InitializeParticle( part );
        }
        currentPart += threashold;
        return;
    }
    else if( currentPart < prototype->maxParts )//more particles fit in but they
    { //are less tha creationThreashold
        for( int i = 0; i < ( prototype->maxParts - currentPart ) && current != particles.end( ); i++, ++current )
        {
            Particle* part = *current;
            InitializeParticle( part );
        }
        currentPart = prototype->maxParts;
        return;
    }
}

void ParticleEmiter::Render(sdl::GraphicsCore* gCore, const gl::Texture& rsc, const Math::Rect& clip, const Math::Rect& camera,gl::BlendingMethodName blending)
{
    bool flip[2] = { false, false };
    if( !active )//if emiter is deactivated extra measurements must be taken for dead particles
    {
        std::list<Particle*>::iterator it;
        for( it = particles.begin( ); it != particles.end( ); ++it )
        {
            if( !( *it )->IsAlive( prototype->lifetime ) ) continue; //if dead do not blit it
            //if (camera.Intersect((*it)->GetPosition()))//if it is inside camera blit it.
            //rsc->Blit(pos.GetX() - camera.GetLeft(), pos.GetY() - camera.GetTop(), target);
            Math::Vector2F pos( ( *it )->GetPosition( ).GetX( ) - camera.GetLeft( ), ( *it )->GetPosition( ).GetY( ) - camera.GetTop( ) );
            gCore->Render( rsc, clip, ( *it )->GetColor( ), pos, ( *it )->GetScale( ), ( *it )->GetAngle( ), flip, blending );
        }
    }
    else
    {
        std::list<Particle*>::iterator it;
        for( it = particles.begin( ); it != particles.end( ); ++it )
        {
            Math::Vector2F pos( ( *it )->GetPosition( ).GetX( ) - camera.GetLeft( ), ( *it )->GetPosition( ).GetY( ) - camera.GetTop( ) );
            gCore->Render( rsc, clip, ( *it )->GetColor( ), pos, ( *it )->GetScale( ), ( *it )->GetAngle( ), flip, blending );
        }
    }
}

void ParticleEmiter::RenderWrapped(sdl::GraphicsCore* gCore, const gl::Texture& rsc, const Math::Rect& clip, const Math::Rect& camera, gl::BlendingMethodName blending)
{
    bool flip[2] = { false, false };

    int left = camera.GetWidth( ) == 0 ? 1 : camera.GetWidth( );
    int top = camera.GetHeight( ) == 0 ? 1 : camera.GetHeight( );

    if( !active )//if emiter is deactivated extra measurements must be taken for dead particles
    {
        std::list<Particle*>::iterator it;
        for( it = particles.begin( ); it != particles.end( ); ++it )
        {
            if( !( *it )->IsAlive( prototype->lifetime ) ) continue; //if dead do not blit it
            Math::Vector2F pos( ( int ) ( *it )->GetPosition( ).GetX( ) % left, ( int ) ( *it )->GetPosition( ).GetY( ) % top );
            gCore->Render( rsc, clip, ( *it )->GetColor( ), pos, ( *it )->GetScale( ), ( *it )->GetAngle( ), flip, blending );
        }
    }
    else
    {
        std::list<Particle*>::iterator it;
        for( it = particles.begin( ); it != particles.end( ); ++it )
        {
            Math::Vector2F pos( ( int ) ( *it )->GetPosition( ).GetX( ) % left, ( int ) ( *it )->GetPosition( ).GetY( ) % top );
            gCore->Render( rsc, clip, ( *it )->GetColor( ), pos, ( *it )->GetScale( ), ( *it )->GetAngle( ), flip, blending );
        }
    }
}

void ParticleEmiter::ApplyForce(forces::Force* force)
{
    std::list<Particle*>::iterator particle;
    for( particle = particles.begin( ); particle != particles.end( ); ++particle )
    {
        force->Affect( ( *particle ) );
    }
}

bool ParticleEmiter::Update(float seconds, Rect* clip)
{
    /*
     * if deactivated instead of recycling dead particles we count how many of them
     * are dead. If all particles are dead then its time this emiter let it go
     * and kill itself and return false.
     */

    // determine the method by wich the alive check will be made.
    AbstractAliveCheck* checker = NULL;

    if( boundingShape != NULL )
    {
        //calibrate shapes position
        boundingShape->SetCenter( position );
        //check if timed upadate is required also
        if( prototype->updateStyle )
        {
            checker = new TimedBoundingShapeAliveCheck( prototype->lifetime, boundingShape );
        }
        else
        {
            checker = new BoundingShapeAliveCheck( boundingShape );
        }
    }
    else
    {
        checker = new TimedAliveCheck( prototype->lifetime );
    }

    if( !active )
    {
        int toRecycle = 0;

        ParticleList::iterator it;
        for( it = particles.begin( ); it != particles.end( ); ++it )
        {
            if( !( *checker )( *it ) )//if dead
            {
                toRecycle++;
            }
            else
            {
                ( *it )->Update( seconds );
                ( *it )->UpdateColor( prototype->birthColor, prototype->deathColor, prototype->lifetime );
                ( *it )->UpdateScale( prototype->birthScale, prototype->deathScale, prototype->lifetime );

            }
        }
        //if all particles are dead kill emiter
        if( toRecycle == prototype->maxParts )
        {
            alive = false;
            delete checker;
            return false;
        }
    }
    else
    {
        ParticleList::iterator it;
        for( it = particles.begin( ); it != particles.end( ); ++it )
        {
            if( !( *checker )( *it ) )//if dead time to recycle
            {
                ( *it )->SetCurrentLife( 0 );
                this->InitializeParticle( ( *it ) );
            }
            else
            {
                ( *it )->Update( seconds );
                ( *it )->UpdateColor( prototype->birthColor, prototype->deathColor, prototype->lifetime );
                ( *it )->UpdateScale( prototype->birthScale, prototype->deathScale, prototype->lifetime );
            }
        }
    }
    delete checker;
    return true;
}

void ParticleEmiter::TileCollisionCheck(LevelMap* map)
{
    TimedAliveCheck checker( prototype->lifetime );
    ParticleList::iterator it;
    for( it = particles.begin( ); it != particles.end( ); ++it )
    {
        if( checker( *it ) )//if particle is alive
        {
            if( !( map->GetHeight( ) * map->GetTileHeight( ) > ( *it )->GetPosition( ).GetY( ) ) ||
                    !( map->GetWidth( ) * map->GetTileWidth( ) > ( *it )->GetPosition( ).GetX( ) ) ) continue;

            if( ( 0 > ( *it )->GetPosition( ).GetY( ) ) || ( 0 > ( *it )->GetPosition( ).GetX( ) ) ) continue;

            if( map->GetTilelistPropsFromRPos( ( *it )->GetPosition( ).GetX( ) + 7, ( *it )->GetPosition( ).GetY( ) + 7 ) & 1 )
            {
                //if this emiter is active recycle particle
                if( this->active )
                {
                    ( *it )->SetCurrentLife( 0 );
                    InitializeParticle( ( *it ) );
                }
                else//kill the particle
                {
                    //HACK the easiest way to kill a particle is
                    //by setting a very big number as lifetime
                    ( *it )->SetCurrentLife( 999 );
                }
            }
        }
    }
}

void ParticleEmiter::InitializeParticle(Particle * part)
{
    part->SetMass( Random( prototype->minMass, prototype->maxMass ) );
    part->SetColor( prototype->birthColor );
    part->SetScale( prototype->birthScale );
    part->SetScaleVariation( Random( 0.0f, prototype->scaleVariation ) );
    /*part->SetAcceleration(0, 0);*/

    {//Initialize force (set initial acceleration based on mass)
        float forceMagnitude = Random( prototype->minInitialForceMagnitude, prototype->maxInitialForceMagnitude );
        int forceDirection = Random( prototype->minInitialForceAngle, prototype->maxInitialForceAngle );
        if( Math::EventCheck( 50 ) )
        {
            //forceDirection* -1;
        }

        part->AddForce( direction.GetRotatedVector( forceDirection ) * forceMagnitude );
    }

    {//Initialize Velocity
        float velocityMagnitude = Random( prototype->minInitialVelocityMagnitude, prototype->maxInitialVelocityMagnitude );
        int velocityDirection = Random( prototype->minInitialVelocityAngle, prototype->maxInitialVelocityAngle );
        if( Math::EventCheck( 50 ) )
        {
            //velocityDirection* -1;
        }
        part->SetVelocity( direction.GetRotatedVector( velocityDirection ) * velocityMagnitude );
    }

    {//Initialize position
        float x = Random( position.GetX( ) - prototype->spawnRadius.GetX( ), position.GetX( ) + prototype->spawnRadius.GetX( ) );
        float y = Random( position.GetY( ) - prototype->spawnRadius.GetY( ), position.GetY( ) + prototype->spawnRadius.GetY( ) );
        part->SetPosition( x, y );
    }

    part->SetCurrentLife( 0 );
}

void ParticleEmiter::Resurect(const Vector2F& pos, const Vector2F& dir, int id, bool wipe/* = false*/)
{
    this->id = id;
    alive = true;
    active = true;
    //TODO:activate emiter
    position = pos;
    direction = dir;
    if( wipe )
    {
        WipeData( );
    }
    else
    {
        recycler = true;
        currentPart = 0;
        current = particles.begin( );
    }
}

void ParticleEmiter::WipeData()
{
    SafeRelease<Particle*>( particles );
    currentPart = 0;
}

// <editor-fold defaultstate="collapsed" desc="geters setters">

void ParticleEmiter::Kill()
{
    active = false;
}

bool ParticleEmiter::IsAlive()
{
    return alive;
}

int ParticleEmiter::getId()
{
    return id;
}

void ParticleEmiter::setId(int id)
{
    this->id = id;
}

void ParticleEmiter::Move(float x, float y)
{
    position.Move( x, y );
}

void ParticleEmiter::SetActive(bool active)
{
    this->active = active;
}

bool ParticleEmiter::IsActive() const
{
    return active;
}

void ParticleEmiter::SetAlive(bool alive)
{
    this->alive = alive;
}

void ParticleEmiter::SetAsRecycler(bool create)
{
    this->recycler = create;
}

bool ParticleEmiter::IsRecycler() const
{
    return recycler;
}

void ParticleEmiter::SetDirection(Math::Vector2F direction)
{
    this->direction = direction;
}

Math::Vector2F ParticleEmiter::GetDirection() const
{
    return direction;
}

void ParticleEmiter::SetPosition(Vector2F position)
{
    this->position = position;
}

Vector2F ParticleEmiter::GetPosition() const
{
    return position;
}
// </editor-fold>

}
}
